package net.dybai.glacier;

import com.amazonaws.auth.profile.ProfileCredentialsProvider;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.glacier.AmazonGlacier;
import com.amazonaws.services.glacier.AmazonGlacierClient;
import com.amazonaws.services.glacier.AmazonGlacierClientBuilder;
import net.dybai.glacier.aws.Upload;
import org.apache.http.util.TextUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Set;

public class Backup {
    private static final Logger LOG = LoggerFactory.getLogger(Backup.class);

    private Thread tCompress = null;
    private Thread tEncrypt = null;
    private Thread tUpload = null;

    public void backup(String vaultName, String region, File srcFile, String password
            , boolean isShowSecret, int partSize, String archiveDescription
            , Set<String> excludeAbsFiles, Set<String> excludeWildcardFiles) {
        PipedInputStream compressPipeIn = new PipedInputStream(partSize);
        PipedOutputStream compressPipeOut = new PipedOutputStream();

        PipedInputStream encryptPipeIn = new PipedInputStream(partSize);
        PipedOutputStream encryptPipeOut = new PipedOutputStream();

        try {
            compressPipeOut.connect(compressPipeIn);
            encryptPipeOut.connect(encryptPipeIn);

            tCompress = new Thread(new CompressRunnable(srcFile, compressPipeOut, excludeAbsFiles, excludeWildcardFiles));
            tEncrypt = new Thread(new EncryptRunnable(compressPipeIn, encryptPipeOut, password, isShowSecret));
            tUpload = new Thread(new UploadRunnable(region, vaultName, encryptPipeIn, partSize, archiveDescription));

            tCompress.start();
            tEncrypt.start();
            tUpload.start();

            tCompress.join();
            tEncrypt.join();
            tUpload.join();
        } catch (Exception e) {
            LOG.error("Backup failed.", e);
        }
    }

    private class CompressRunnable implements Runnable {
        File srcFile = null;
        PipedOutputStream compressPipeOut = null;
        Set<String> excludeAbsFiles = null;
        Set<String> excludeWildcardFiles = null;

        CompressRunnable(File srcFile, PipedOutputStream compressPipeOut
                , Set<String> excludeAbsFiles, Set<String> excludeWildcardFiles) {
            this.srcFile = srcFile;
            this.compressPipeOut = compressPipeOut;
            this.excludeAbsFiles = excludeAbsFiles;
            this.excludeWildcardFiles = excludeWildcardFiles;
        }

        @Override
        public void run() {
            LOG.info("Beginning compress files.");
            try {
                ZipUtils.zip(srcFile, compressPipeOut, excludeAbsFiles, excludeWildcardFiles);
                LOG.info("Compress complete.");
            } catch (Exception e) {
                LOG.error("Compress error.", e);
                if (!(e instanceof InterruptedException)) {
                    if (null != tEncrypt) tEncrypt.interrupt();
                    if (null != tUpload) tUpload.interrupt();
                }
            }
        }
    };

    private class EncryptRunnable implements Runnable {
        private InputStream in = null;
        private OutputStream out = null;
        private String password = null;
        private boolean isShowSecret = false;

        EncryptRunnable(InputStream in, OutputStream out, String password, boolean isShowSecret) {
            this.in = in;
            this.out = out;
            this.password = password;
            this.isShowSecret = isShowSecret;
        }

        @Override
        public void run() {
            LOG.info("Beginning encrypt.");
            try {
                AESUtils.fileEncrypt(in, out, password, isShowSecret);
                LOG.info("Encrypt complete.");
            } catch (Exception e) {
                LOG.error("Encrypt error.", e);
                if (!(e instanceof InterruptedException)) {
                    if (null != tCompress) tCompress.interrupt();
                    if (null != tUpload) tUpload.interrupt();
                }
            } finally {
                if (null != in) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (null != out) {
                    try {
                        out.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    };

    private class UploadRunnable implements Runnable {
        private String region = null;
        private String vaultName = null;
        private InputStream in = null;
        private int partSize;
        private String archiveDescription = null;

        UploadRunnable(String region, String vaultName, InputStream in, int partSize, String archiveDescription) {
            this.region = region;
            this.vaultName = vaultName;
            this.in = in;
            this.partSize = partSize;
            this.archiveDescription = archiveDescription;
        }

        @Override
        public void run() {
            try {
                String location = start(region, vaultName, in, partSize, archiveDescription);
                LOG.info("Upload complete, location: " + location);
            } catch (Exception e) {
                LOG.error("Upload error.", e);
                if (!(e instanceof InterruptedException)) {
                    if (null != tCompress) tCompress.interrupt();
                    if (null != tEncrypt) tEncrypt.interrupt();
                }
            } finally {
                if (null != in) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private String start(String region, String vaultName, InputStream in, int partSize, String archiveDescription)
            throws IOException {
        Upload upload = new Upload();

        ProfileCredentialsProvider credentials = new ProfileCredentialsProvider();
        AmazonGlacierClientBuilder builder = AmazonGlacierClient.builder()
                .withCredentials(credentials)
                .withEndpointConfiguration(new AwsClientBuilder
                        .EndpointConfiguration("https://glacier." + region + ".amazonaws.com/", region));
        AmazonGlacier glacier = builder.build();

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String formatDate = sdf.format(new Date());
        String defArchiveDesc = "Backup by " + formatDate;
        if (!TextUtils.isEmpty(archiveDescription)) {
            defArchiveDesc = archiveDescription;
        }

        String accountId = "-";
        String checksum = null;

        LOG.info("Initiate multipart upload.");
        String uploadId = upload.initiateMultipartUpload(glacier, accountId,vaultName, partSize + ""
                , defArchiveDesc);
        LOG.info("Beginning upload, uploadId: " + uploadId);
        try {
            checksum = upload.uploadParts(glacier, accountId, vaultName, partSize, uploadId, in);
            LOG.info("Upload finish, file checksum: " + checksum);
        } catch (Exception e) {
            String abortResponse = upload.abortMultipartUpload(glacier, accountId, vaultName, uploadId);
            LOG.error("Upload failed, Abort multipart upload, Server response: " + abortResponse);
            throw e;
        }
        return upload.completeMultipartUpload(glacier, accountId, vaultName, uploadId, checksum);
    }
}
